/************************************************************************
                                                                     
   Xceed Ultimate ListBox for Silverlight                                                                                                                                            
   Copyright (C) 2010 Xceed Software Inc.    
                                                                     
   This program is provided to you under the terms of the GNU General Public  
   License version 2 as published by the Free Software Foundation. 
        
   This program is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY, without even the implied warranty of 
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.

   You should have received a copy of the GNU General Public License along 
   with this program, if not, write to the Free Software Foundation, Inc., 
   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

   This program can be provided to you by Xceed Software Inc. under a
   proprietary commercial license agreement for use in non-Open Source
   projects. Visit Xceed.com to find the commercial edition of 
   Xceed Ultimate ListBox for Silverlight.                                    
                                                                      
 **********************************************************************/

using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.Linq;
using Xceed.Silverlight.Data.Utils;

namespace Xceed.Silverlight.Data
{
  internal abstract class DataSourceEnumeratorBase : AsyncParentEnumerator<DataPath>, IAsyncDataSourceEnumerator, ISLWeakEventListener
  {
    #region Static Fields

    private static readonly Guid ResetCallId = DataUtils.CreateCallId();
    private static readonly Guid GroupFetchCallId = DataUtils.CreateCallId();
    private static readonly double IndexAdjustmentRatio = ( 1d / 10d );

    #endregion Static Fields

    internal DataSourceEnumeratorBase(
      IAsyncEnumerator<DataPath> childEnumerator,
      IPendingRequests pendingRequestSource,
      IDataHelper<DataPath> dataHelper,
      GroupHelper groupHelper )
      : base( childEnumerator )
    {
      if( dataHelper == null )
        throw new ArgumentNullException( "dataHelper" );

      if( groupHelper == null )
        throw new ArgumentNullException( "groupHelper" );

      m_dataHelper = dataHelper;
      m_groupHelper = groupHelper;
      m_pendingRequestSource = pendingRequestSource;

      if( m_pendingRequestSource != null )
      {
        m_pendingRequestSource.HasPendingRequestsChanged += new EventHandler( OnHasPendingRequestsChanged );
      }

      InternalExpansionChangingEventManager.AddListener( groupHelper.GroupHandler, this );
    }

    #region Index Property

    public long Index
    {
      get
      {
        if( m_disposed )
          throw new ObjectDisposedException( "The enumerator has been disposed." );

        return
          ( m_currentOffset.ReferencePosition == ReferencePoint.BeforeFirst )
          ? m_currentOffset.Value - 1
          : m_itemCount.Value - m_currentOffset.Value;
      }
    }

    #endregion Index Property

    #region ItemCount Property

    public long ItemCount
    {
      get
      {
        if( m_disposed )
          throw new ObjectDisposedException( "The enumerator has been disposed." );

        return m_itemCount.Value;
      }
    }

    #endregion ItemCount Property

    #region ItemCountEstimate Property

    protected EstimateItemCount ItemCountEstimate
    {
      get
      {
        return m_itemCount;
      }
    }

    #endregion ItemCountEstimate Property

    #region CurrentOffset Property

    protected RelativeOffset CurrentOffset
    {
      get
      {
        return m_currentOffset;
      }
    }

    #endregion CurrentOffset Property

    #region CurrentPosition Property

    internal SourcePosition CurrentPosition
    {
      get
      {
        return m_currentPosition;
      }
      private set
      {
        if( value == null )
          throw new ArgumentNullException( "CurrentPosition" );

        m_currentPosition = value;
      }
    }

    #endregion CurrentPosition Property

    #region EnableApproximateMoves Property

    internal bool EnableApproximateMoves
    {
      get
      {
        return m_enableApproximateMoves;
      }

      set
      {
        m_enableApproximateMoves = value;
      }
    }

    #endregion EnableApproximateMoves Property

    #region PreventEstimateIndex Property

    internal bool PreventEstimateIndex
    {
      get;
      set;
    }

    #endregion PreventEstimateIndex

    #region ProviderSupportGroupCount Property

    internal bool ProviderSupportGroupCount
    {
      get;
      set;
    }

    #endregion ProviderSupportGroupCount

    #region IndexChanged Event

    public event EventHandler<IndexChangedEventArgs> IndexChanged;

    protected virtual void RaiseIndexChanged( long oldValue, long newValue )
    {
      if( this.IndexChanged != null )
      {
        this.IndexChanged( this, new IndexChangedEventArgs( oldValue, newValue ) );
      }
    }

    #endregion IndexChanged Event

    #region ItemCountChanged Event

    public event EventHandler ItemCountChanged;

    protected virtual void RaiseItemCountChanged()
    {
      if( this.ItemCountChanged != null )
      {
        this.ItemCountChanged( this, EventArgs.Empty );
      }
    }

    #endregion ItemCountChanged Event

    #region Begin/End BaseJumpMoveAndFetch Methods
    protected JumpMoveAndFetchAsyncResult BeginBaseJumpMoveAndFetch(
      JumpMoveAndFetchParameters parameters,
      AsyncCallback userCallback, object asyncState )
    {
      return base.BeginJumpMoveAndFetch( parameters, userCallback, asyncState );
    }

    protected JumpMoveAndFetchResult<DataPath> EndBaseJumpMoveAndFetch(
      JumpMoveAndFetchAsyncResult asyncResult )
    {
      return base.EndJumpMoveAndFetch( asyncResult );
    }


    #endregion Begin/End BaseJumpMoveAndFetch Methods

    #region Begin/End JumpMoveAndFetch

    public override JumpMoveAndFetchResult<DataPath> EndJumpMoveAndFetch(
      JumpMoveAndFetchAsyncResult asyncResult )
    {
      JumpMoveAndFetchResult<DataPath> result = base.EndJumpMoveAndFetch( asyncResult );

      SourcePosition initialCurrentPosition = m_currentPosition;
      m_currentPosition = result.EndPositionObject;

      int compareResult = m_dataHelper.Compare( initialCurrentPosition, result.SourcePosition );
      FetchStartPosition operationStartPosition = FetchStartPosition.SameAsInitial;

      if( compareResult < 0 )
      {
        operationStartPosition = FetchStartPosition.After;
      }
      else if( compareResult > 0 )
      {
        operationStartPosition = FetchStartPosition.Before;
      }


      HandleDisplacement(
        operationStartPosition,
        result.GetTotalDisplacement(),
        this.HandleUnsupportedEstimateIndex(result.EndPositionOffset),
        result.TotalCount );

      this.AdjustIndexRelativePosition();

      return result;
    }

    #endregion Begin/End JumpMoveAndFetch

    #region Begin/End FetchGroup Methods

    public IAsyncResult BeginFetchGroup(
      DataPath groupPath,
      GroupTarget groupTarget,
      int expectedFetchCount,
      AsyncCallback userCallback,
      object asyncState )
    {
      if( expectedFetchCount == 0 )
        throw new ArgumentException( "expectedFetchCount must be less than or greater than zero.", "expectedFetchCount" );

      ContextAsyncResult parentAsyncResult = new ContextAsyncResult(
        DataSourceEnumeratorBase.GroupFetchCallId,
        userCallback, asyncState );

      parentAsyncResult.CallContext = new Tuple<int, GroupTarget>( expectedFetchCount, groupTarget );

      bool twoFetchOpration = ( ( groupTarget == GroupTarget.Previous ) && ( expectedFetchCount > 0 ) )
        || ( ( groupTarget == GroupTarget.Next ) && ( expectedFetchCount < 0 ) );

      StartPosition fetchStartPosition =
        ( groupTarget == GroupTarget.Next ) || ( ( groupTarget == GroupTarget.Current ) && ( expectedFetchCount < 0 ) )
        ? StartPosition.After
        : StartPosition.Before;

      if( !twoFetchOpration )
      {
        JumpMoveAndFetchParameters parameters = new JumpMoveAndFetchParameters(
          this.CreateGroupFetchSourcePosition( groupPath, fetchStartPosition ),
          0,
          expectedFetchCount,
          false );

        Func<AsyncCallback, object, IAsyncResult> fetchCall =
          ( callback, state ) => this.BeginBaseJumpMoveAndFetch( parameters, callback, state );

        Func<AsyncResult, IAsyncResult, DependentCallResult> fetchCallback =
          ( parentAr, ar ) =>
          {
            JumpMoveAndFetchResult<DataPath> result = this.EndBaseJumpMoveAndFetch( ( JumpMoveAndFetchAsyncResult )ar );
            GroupFetchResult groupFetchResult = new GroupFetchResult(
              result.FetchedItems, result.EndPositionObject,
              result.EndPositionOffset, result.TotalCount );

            return new DependentCallResult( groupFetchResult );
          };

        parentAsyncResult.Start( fetchCall, fetchCallback );
      }
      else
      {

        JumpMoveAndFetchParameters firstCallParams;
        JumpMoveAndFetchParameters secondCallParams;
        Func<AsyncCallback, object, IAsyncResult> firstCall;
        Func<AsyncCallback, object, IAsyncResult> secondCall;
        Func<AsyncResult, IAsyncResult, DependentCallResult> firstCallback;
        Func<AsyncResult, IAsyncResult, DependentCallResult> secondCallback;

        firstCallParams = new JumpMoveAndFetchParameters(
          this.CreateGroupFetchSourcePosition( groupPath, fetchStartPosition ),
          0, -DataUtils.GetUnit( expectedFetchCount ), false );

        firstCall = ( callback, state ) =>
            this.BeginBaseJumpMoveAndFetch( firstCallParams, callback, state );

        secondCallback = ( parentAr, ar ) =>
        {
          JumpMoveAndFetchResult<DataPath> result = this.EndBaseJumpMoveAndFetch( ( JumpMoveAndFetchAsyncResult )ar );

          JumpMoveAndFetchResult<DataPath> firstCallResult = ar.AsyncState as JumpMoveAndFetchResult<DataPath>;

          if( firstCallResult == null )
            throw new DataInternalException();

          EstimateInt64 totalCount =
            ( result.TotalCount != null )
            ? result.TotalCount
            : firstCallResult.TotalCount.ToEstimate();

          GroupFetchResult groupFetchResult = new GroupFetchResult(
            result.FetchedItems, result.EndPositionObject, result.EndPositionOffset, totalCount );

          return new DependentCallResult( groupFetchResult );
        };

        firstCallback = ( parentAr, ar ) =>
        {
          JumpMoveAndFetchResult<DataPath> adjacentItem = this.EndBaseJumpMoveAndFetch( ( JumpMoveAndFetchAsyncResult )ar );
          if( adjacentItem.FetchedItems == null )
          {
            GroupFetchResult groupFetchResult = new GroupFetchResult(
              null, adjacentItem.EndPositionObject,
              adjacentItem.EndPositionOffset, adjacentItem.TotalCount );

            return new DependentCallResult( groupFetchResult );
          }
          else
          {
            DataPath adjacentGroupPath = adjacentItem.FetchedItems.First();
            if( adjacentGroupPath.Depth > groupPath.Depth )
            {
              adjacentGroupPath = adjacentGroupPath.CreateAncestorPath( groupPath.Depth );
            }

            secondCallParams = new JumpMoveAndFetchParameters(
              this.CreateGroupFetchSourcePosition( adjacentGroupPath, fetchStartPosition ),
              0, expectedFetchCount, false );

            secondCall = ( callback, state ) =>
              this.BeginBaseJumpMoveAndFetch( ( JumpMoveAndFetchParameters )secondCallParams, callback, state );

            return new DependentCallResult( adjacentItem, secondCall, secondCallback );
          }
        };

        parentAsyncResult.Start( firstCall, firstCallback );
      }

      return parentAsyncResult;
    }

    public GroupFetchResult EndFetchGroup( IAsyncResult asyncResult )
    {
      asyncResult.ValidateCallEnd( DataSourceEnumeratorBase.GroupFetchCallId );

      GroupFetchResult result = asyncResult.GetCallResult<GroupFetchResult>();

      Tuple<int, GroupTarget> callContext = asyncResult.GetCallContext<Tuple<int, GroupTarget>>();

      int expectedFetchCount = callContext.Item1;
      GroupTarget groupTarget = callContext.Item2;

      m_currentPosition = result.EndPositionObject;

      FetchStartPosition operationStartPosition;

      if( groupTarget == GroupTarget.Previous )
      {
        operationStartPosition = FetchStartPosition.Before;
      }
      else if( groupTarget == GroupTarget.Next )
      {
        operationStartPosition = FetchStartPosition.After;
      }
      else //(groupTarget == GroupTarget.Current)
      {
        Debug.Assert( groupTarget == GroupTarget.Current );

        //StartPosition is "before" if we fetch the group top,
        //and "after" if we fetched the group end.
        if( expectedFetchCount < 0 )
        {
          operationStartPosition = FetchStartPosition.After;
        }
        else
        {
          operationStartPosition = FetchStartPosition.Before;
        }
      }

      HandleDisplacement(
        operationStartPosition,
        result.GetFetchDisplacement( expectedFetchCount ),
        this.HandleUnsupportedEstimateIndex( result.EndPositionOffset ),
        result.TotalCount );

      this.AdjustIndexRelativePosition();

      return result;
    }

    public virtual SourcePosition CreateGroupFetchSourcePosition( DataPath groupPath, StartPosition fetchStartPosition )
    {
      return m_groupHelper.CreateSortPositionForGroup( groupPath, fetchStartPosition );
    }

    #endregion Begin/End FetchGroup Methods

    #region Begin/End MoveAndFetch

    public IMoveAndFetchAsyncResult BeginMoveAndFetch(
      bool approximateMove,
      long expectedMoveCount,
      int expectedFetchCount,
      AsyncCallback userCallback,
      object asyncState )
    {
      approximateMove = approximateMove && this.EnableApproximateMoves;

      JumpMoveAndFetchParameters parameters = new JumpMoveAndFetchParameters(
                                                    m_currentPosition,
                                                    expectedMoveCount,
                                                    expectedFetchCount,
                                                    approximateMove );

      return this.BeginJumpMoveAndFetch(
               parameters,
               userCallback,
               asyncState );
    }

    public MoveAndFetchResult<DataPath> EndMoveAndFetch(
      IMoveAndFetchAsyncResult asyncResult )
    {
      JumpMoveAndFetchAsyncResult jumpAsyncResult = ( JumpMoveAndFetchAsyncResult )asyncResult;
      JumpMoveAndFetchResult<DataPath> result = this.EndJumpMoveAndFetch( jumpAsyncResult );

      return new MoveAndFetchResult<DataPath>(
                   result.ActualMoveCount,
                   result.FetchedItems,
                   result.ApproximateMove,
                   result.ExpectedMoveCount,
                   result.ExpectedFetchCount,
                   result.Result,
                   result.EndPositionOffset,
                   result.TotalCount );
    }

    #endregion Begin/End MoveAndFetch

    #region Begin/End Reset

    public IAsyncResult BeginReset( AsyncCallback userCallback, object asyncState )
    {
      AsyncResult asyncResult = new AsyncResult( DataSourceEnumeratorBase.ResetCallId, userCallback, asyncState );
      asyncResult.StartSync( new ResetResult() );
      return asyncResult;

    }

    public ResetResult EndReset( IAsyncResult resetAsyncResult )
    {
      resetAsyncResult.ValidateCallEnd( DataSourceEnumeratorBase.ResetCallId );
      m_currentPosition = SourcePosition.BeforeFirst;
      return resetAsyncResult.GetCallResult<ResetResult>();
    }

    #endregion Begin/End Reset

    internal RelativeOffset HandleUnsupportedEstimateIndex( RelativeOffset endPositionOffset )
    {
      GroupHandler groupHandler = m_groupHelper.GroupHandler;
      if( this.PreventEstimateIndex && endPositionOffset != null && endPositionOffset.IsEstimate )
      {
        bool estimateByHeadersFooters = ( !this.ProviderSupportGroupCount && groupHandler.HasHeadersFooters );
        bool estimateByCollapsedGroups = ( ( groupHandler.GroupCountPolicy == GroupCountPolicy.DoNotHandleCounts ) && groupHandler.HasCollapsedGroups );

        if( estimateByCollapsedGroups || estimateByHeadersFooters )
        {
          endPositionOffset = null;
        }
      }

      return endPositionOffset;
    }


    public virtual bool IsFirst( DataPath path, int levelDepth )
    {
      if( path == null )
        throw new ArgumentNullException( "path" );

      if( levelDepth < 0 || levelDepth >= path.Depth )
        throw new ArgumentOutOfRangeException( "levelDepth", "levelDepth must be greater than or equal to zero and less than the path depth." );

      if( !this.IsFullySynchronized )
        throw new InvalidOperationException( "An attempt was made to use IsFirst while the enumerator is not fully synchronized." );

      JumpMoveAndFetchParameters parameters;

      if( m_groupHelper.IsGroupPath( path ) && !m_groupHelper.GroupHandler.IsCollapsed( path ) )
      {
        parameters = new JumpMoveAndFetchParameters(
          m_groupHelper.CreateSortPositionForGroup( path, StartPosition.Before ),
          0,
          -1 );
      }
      else
      {
        parameters = new JumpMoveAndFetchParameters(
          new ItemPosition( path ),
          -1,
          -1 );
      }




      JumpMoveAndFetchAsyncResult asyncResult = this.ChildEnumeratorTyped.BeginJumpMoveAndFetch(
                                                parameters,
                                                null,
                                                null ).EnsureSyncness();

      JumpMoveAndFetchResult<DataPath> result = this.ChildEnumeratorTyped.EndJumpMoveAndFetch( asyncResult );

      if( result.FetchedItems == null )
      {
        return true;
      }

      if( levelDepth == 0 )
      {
        return false;
      }

      DataPath previousPath = result.FetchedItems.First();
      bool isFirst = false;

      for( int i = 1; i <= levelDepth; i++ )
      {
        if( previousPath.Depth < i )
        {
          isFirst = true;
          break;
        }

        if( !object.Equals( previousPath[ i - 1 ], path[ i - 1 ] ) )
        {
          isFirst = true;
          break;
        }
      }

      return isFirst;
    }

    protected virtual void ExpansionChanging(
      object sender,
      ExpansionChangedEventArgs e )
    {
      ItemPosition currentPosition = this.CurrentPosition as ItemPosition;

      if( currentPosition != null )
      {
        DataPath path = ( DataPath )currentPosition.Item;

        if( e.Action == ExpansionAction.CollapseAll || e.Action == ExpansionAction.Collapse )
        {
          DataPath newCollapsedCurrent = null;
          if( e.Action == ExpansionAction.Collapse )
          {
            if( e.Path.IsAncestorOf( path ) )
            {
              newCollapsedCurrent = e.Path;
            }
          }
          else 
          {
            DataPath groupingPath = m_groupHelper.GroupHandler.GetGroupingPath( path );

            if( e.ExpansionDepth <= groupingPath.Depth )
            {
              newCollapsedCurrent = path.CreateAncestorOrSamePath( e.ExpansionDepth );
            }
          }

          if( newCollapsedCurrent != null )
          {
            //The current is an item of the newly collapsed group.
            //Set it at the head of the group so we can see the collapsed header of the group.
            //Otherwise, the current would be skipped directly after the collapsed group.
            this.CurrentPosition = new ItemPosition( newCollapsedCurrent );
          }
        }
        else
        {
          Debug.Assert( e.Action == ExpansionAction.Expand || e.Action == ExpansionAction.ExpandAll );

          DataPath groupingPath = m_groupHelper.GroupHandler.GetGroupingPath( path );

          if( groupingPath.Depth == path.Depth )
          {
            if( ( ( e.Action == ExpansionAction.Expand ) && e.Path.Equals( path ) )
              || ( ( e.Action == ExpansionAction.ExpandAll ) && ( groupingPath.Depth == e.ExpansionDepth ) ) )
            {
              this.CurrentPosition = this.m_groupHelper.CreateSortPositionForGroup( path, StartPosition.Before );
            }
          }
        }
      }
    }

    protected override void ChildDataChanged( DataChangedEventArgs args )
    {
      if( m_disposed )
        throw new ObjectDisposedException( "The enumerator has been disposed." );

      if( args.Action == DataChangedAction.Replace )
      {
        this.HandleCurrentReplace( ( DataPath )args.OldItem, ( DataPath )args.NewItem );
      }
      else if( args.Action == DataChangedAction.Add )
      {
        RelativeOffset newOffset = m_currentOffset;

        if( m_dataHelper.Compare( m_currentPosition, args.NewItem ) > 0 )
        {
          newOffset = DataUtils.CalculateRelativeOffset( m_currentOffset, 1 );
        }

        this.SetCountValues( newOffset, new EstimateItemCount( m_itemCount.Value + 1, m_itemCount.IsEstimate, m_itemCount.IsBasedOnOffset ) );
      }
      else if( args.Action == DataChangedAction.Remove )
      {
        RelativeOffset dataSourceOffset;
        RelativeOffset calculatedOffset;
        SourcePosition newCurrentPosition;

        this.HandleCurrentRemove( ( DataPath )args.OldItem, args.RemovePosition, out newCurrentPosition, out dataSourceOffset );

        if( m_dataHelper.Compare( m_currentPosition, args.OldItem ) > 0 )
        {
          calculatedOffset = DataUtils.CalculateRelativeOffset( m_currentOffset, -1 );
        }
        else
        {
          calculatedOffset = m_currentOffset;
        }

        RelativeOffset newOffset = ( dataSourceOffset != null ) ? dataSourceOffset : calculatedOffset;
        EstimateItemCount newCount = new EstimateItemCount( m_itemCount.Value - 1, m_itemCount.IsEstimate, m_itemCount.IsBasedOnOffset );

        if( dataSourceOffset != null && newCount.IsBasedOnOffset )
        {
          newCount = DataSourceEnumeratorBase.CalculateNewCountBasedOnOffsets( calculatedOffset, dataSourceOffset, newCount );
        }

        this.SetCountValues( newOffset, newCount );

        args = new DataChangedEventArgs(
             DataChangedAction.Remove,
             args.OldItem,
             args.RemovePosition,
             newCurrentPosition );
      }

      base.ChildDataChanged( args );
    }

    protected bool HandleCurrentRemove(
      DataPath removed,
      SourcePosition removePosition,
      out SourcePosition newCurrentPosition,
      out RelativeOffset newCurrentOffset )
    {
      if( removed == null )
        throw new ArgumentNullException( "removed" );

      newCurrentPosition = null;
      newCurrentOffset = null;

      bool isCurrentItem = false;
      SourcePosition currentSourcePosition = m_currentPosition;

      // Figure out if the removed item is the current item.
      if( currentSourcePosition is DummyPosition<DataPath> )
      {
        DummyPosition<DataPath> dummyPosition = ( DummyPosition<DataPath> )currentSourcePosition;

        if( dummyPosition.BasedOnItem )
        {
          isCurrentItem = object.Equals( dummyPosition.BaseItem, removed );
        }
        else
        {
          currentSourcePosition = dummyPosition.BaseSourcePosition;
        }
      }

      if( currentSourcePosition is ItemPosition )
      {
        ItemPosition itemPosition = ( ItemPosition )currentSourcePosition;

        isCurrentItem = object.Equals( itemPosition.Item, removed );
      }

      // The current item has been removed.  We must replace it by a valid SourcePosition.
      if( isCurrentItem )
      {
        if( removePosition == SourcePosition.BeforeFirst )
        {
          m_currentPosition = SourcePosition.BeforeFirst;
          newCurrentPosition = SourcePosition.BeforeFirst;
          newCurrentOffset = new RelativeOffset( 0, ReferencePoint.BeforeFirst );
        }
        else if( removePosition == SourcePosition.AfterLast )
        {
          m_currentPosition = SourcePosition.AfterLast;
          newCurrentPosition = SourcePosition.AfterLast;
          newCurrentOffset = new RelativeOffset( 0, ReferencePoint.AfterLast );
        }
        else
        {
          Debug.Assert( removePosition == null );
          //If the current is the deleted item, the next fetch will take
          //the previous or following items in the source.
        }
      }

      return ( newCurrentPosition != null );
    }

    protected virtual bool HandleCurrentReplace( DataPath oldItem, DataPath newItem )
    {
      DataPath currentDataPath;
      if( !this.TryGetCurrentDataPath( out currentDataPath ) )
        return false;

      bool isSameDataPath = object.Equals( currentDataPath, oldItem );
      if( isSameDataPath )
      {
        Debug.Assert( newItem != null );
        m_currentPosition = new ItemPosition( newItem );
      }

      return isSameDataPath;
    }

    private bool TryGetCurrentDataPath( out DataPath dataPath )
    {
      dataPath = null;

      ItemPosition itemPosition = m_currentPosition as ItemPosition;
      if( itemPosition != null )
      {
        dataPath = itemPosition.Item as DataPath;
      }

      DummyPosition<DataPath> dummyPosition = m_currentPosition as DummyPosition<DataPath>;
      if( ( dummyPosition != null ) && ( dummyPosition.BasedOnItem ) )
      {
        dataPath = dummyPosition.BaseItem;
      }

      return ( dataPath != null );
    }


    #region Index and Count Management

    internal void SetCountValues( RelativeOffset newOffset, EstimateItemCount newCount )
    {
      long oldIndex = this.Index;
      long oldCount = this.ItemCount;

      if( newOffset != null )
      {
        m_currentOffset = newOffset;
      }

      if( newCount != null )
      {
        m_itemCount = newCount;
      }

      if( oldCount != this.ItemCount )
      {
        this.RaiseItemCountChanged();
      }

      if( oldIndex != this.Index )
      {
        this.RaiseIndexChanged( oldIndex, this.Index );
      }
    }

    private void HandleDisplacement(
      FetchStartPosition fetchStartPosition,
      EstimateInt64 totalDisplacement,
      RelativeOffset limitOffset,
      EstimateInt64 totalCount )
    {

      Debug.Assert( totalDisplacement != null || ( limitOffset != null && !limitOffset.IsEstimate ),
        "If total displacement is null, the move should have failed, hence having a precise position" );

      RelativeOffset newOffset;
      RelativeOffset calculatedOffset = null;
      RelativeOffset dataSourceOffset = null;

      EstimateItemCount newCount;

      if( totalCount != null )
      {
        newCount = new EstimateItemCount( totalCount, false );
      }
      else
      {
        newCount = new EstimateItemCount( this.ItemCount, true, m_itemCount.IsBasedOnOffset );
      }

      if( limitOffset != null && !limitOffset.IsEstimate )
      {
        dataSourceOffset = limitOffset;
      }


      if( dataSourceOffset == null || newCount.IsBasedOnOffset )
      {
        RelativeOffset initialOffset = m_currentOffset.ToEstimate();

        if( fetchStartPosition == FetchStartPosition.After )
        {
          initialOffset = DataUtils.CalculateRelativeOffset( initialOffset, ( this.ItemCount - this.Index ) / 2 );
        }
        else if( fetchStartPosition == FetchStartPosition.Before )
        {
          double adjustment = Math.Floor( ( ( double )this.Index ) / 2 );
          initialOffset = DataUtils.CalculateRelativeOffset( initialOffset, -Convert.ToInt64( adjustment ) );
        }

        calculatedOffset = DataUtils.CalculateRelativeOffset( initialOffset, totalDisplacement.Value, false );

        if( dataSourceOffset != null && newCount.IsBasedOnOffset )
        {
          newCount = DataSourceEnumeratorBase.CalculateNewCountBasedOnOffsets( calculatedOffset, dataSourceOffset, newCount );
        }
      }

      newOffset = ( dataSourceOffset != null ) ? dataSourceOffset : calculatedOffset;

      Debug.Assert( newOffset != null );

      if( !this.IsCountValuesValid( newOffset, newCount ) )
      {
        this.HandleInconsistentCountData( ref newOffset, ref newCount );

        if( !this.IsCountValuesValid( newOffset, newCount ) )
        {
          throw new DataInternalException(); //The data-count information is invalid.
        }
      }

      if( newOffset.IsEstimate && newOffset.Value < 1 )
      {
        long adjustment = ( 1 - newOffset.Value );
        newOffset += adjustment;

        if( newCount.IsBasedOnOffset )
        {
          Debug.Assert( newCount.IsEstimate );
          newCount = new EstimateItemCount( newCount.Value + adjustment, true, true );
        }
      }

      if( newOffset.Value > newCount.Value )
      {
        if( ( !newOffset.IsEstimate || newCount.IsBasedOnOffset ) && newCount.IsEstimate )
        {
          newCount = new EstimateItemCount( newOffset.Value, true, newCount.IsBasedOnOffset );
        }
        else if( newOffset.IsEstimate )
        {
          newOffset = new RelativeOffset( new EstimateInt64( newCount.Value, true ), newOffset.ReferencePosition );
        }
      }

      this.SetCountValues( newOffset, newCount );
    }


    protected virtual void HandleInconsistentCountData( ref RelativeOffset currentOffset, ref EstimateItemCount itemCount )
    {
      throw new DataInternalException(); //The data-count information is invalid.
    }

    protected static EstimateItemCount CalculateNewCountBasedOnOffsets( RelativeOffset expectedOffset, RelativeOffset providedOffset, EstimateItemCount count )
    {
      if( expectedOffset == null )
        throw new ArgumentNullException( "expectedOffset" );

      if( providedOffset == null )
        throw new ArgumentNullException( "providedOffset" );

      if( count == null )
        throw new ArgumentNullException( "count" );

      if( !count.IsBasedOnOffset )
        throw new DataInternalException(); //A new count with a more precise item count was received.

      EstimateItemCount newCount;

      if( expectedOffset.ReferencePosition == providedOffset.ReferencePosition )
      {
        long diff = expectedOffset.Value - providedOffset.Value;
        newCount = new EstimateItemCount( Math.Max( 0, count.Value - diff ), true, true );
      }
      else
      {
        long total = expectedOffset.Value + providedOffset.Value;
        newCount = new EstimateItemCount( total, true, true );
      }

      return newCount;
    }

    protected virtual void AdjustIndexRelativePosition()
    {
      if( !m_itemCount.IsEstimate && !m_currentOffset.IsEstimate )
        return;



      RelativeOffset newOffset = m_currentOffset;
      EstimateItemCount newCount = m_itemCount;
      Func<long, long> itemCountAdjustment = null;
      Func<long, long> indexAdjustment = null;


      itemCountAdjustment = ( count ) => Convert.ToInt64( Math.Ceiling(
        ( DataSourceEnumeratorBase.IndexAdjustmentRatio * count ) /
        ( 1 - DataSourceEnumeratorBase.IndexAdjustmentRatio )
        ) );

      indexAdjustment = ( count ) => Convert.ToInt64( Math.Ceiling(
          ( ( double )count ) * DataSourceEnumeratorBase.IndexAdjustmentRatio
          ) );



      long topOffsetAdjustmentLimit =
        ( newCount.IsBasedOnOffset )
        ? newCount.Value - Math.Max( 1, indexAdjustment( newCount.Value ) / 2 )
        : newCount.Value;

      if( newCount.IsEstimate && ( newCount.IsBasedOnOffset || !newOffset.IsEstimate ) )
      {
        if( newOffset.IsEstimate && newOffset.Value <= 1 )
        {
          newCount = new EstimateItemCount( newCount.Value + itemCountAdjustment( newCount.Value ), newCount.IsEstimate, newCount.IsBasedOnOffset );
        }
        else if( newOffset.Value > topOffsetAdjustmentLimit )
        {
          newCount = new EstimateItemCount( newOffset.EstimateValue.Value + itemCountAdjustment( newOffset.EstimateValue.Value ), true, newCount.IsBasedOnOffset );
        }
      }

      if( newOffset.IsEstimate )
      {
        if( newOffset.Value <= 1 )
        {
          newOffset = new RelativeOffset(
            new EstimateInt64( indexAdjustment( newCount.Value ), true ),
            m_currentOffset.ReferencePosition );
        }
        else if( newOffset.Value > topOffsetAdjustmentLimit )
        {
          newOffset = new RelativeOffset(
            new EstimateInt64( newCount.Value - indexAdjustment( newCount.Value ), true ),
            m_currentOffset.ReferencePosition );
        }
      }

      if( newOffset.IsEstimate && newCount.Value > 0
        && ( newOffset.Value <= 1 || newOffset.Value >= newCount.Value ) )
      {
        newOffset = new RelativeOffset(
          Math.Max( 1, Math.Min( newCount.Value - 1, newOffset.Value ) ),
          newOffset.ReferencePosition );
      }

      this.SetCountValues( newOffset, newCount );
    }

    private bool IsCountValuesValid( RelativeOffset currentOffset, EstimateItemCount itemCount )
    {
      bool currentOffsetIsNegativeError = false;
      bool currentOffsetIsToBigError = false;

      if( currentOffset != null && !currentOffset.IsEstimate )
      {
        currentOffsetIsNegativeError = ( currentOffset.Value < 0 );

        if( itemCount != null && !itemCount.IsEstimate )
        {
          currentOffsetIsToBigError = ( currentOffset.Value > itemCount.Value + 1 );
        }
      }

      return !currentOffsetIsNegativeError && !currentOffsetIsToBigError;
    }

    #endregion Index and Count  Management

    #region IPendingRequests Members

    public bool HasPendingRequests
    {
      get
      {
        return ( m_pendingRequestSource != null )
          ? m_pendingRequestSource.HasPendingRequests
          : false;
      }
    }

    protected virtual void OnHasPendingRequestsChanged( object sender, EventArgs e )
    {
      if( this.HasPendingRequestsChanged != null )
      {
        this.HasPendingRequestsChanged( this, EventArgs.Empty );
      }
    }

    public event EventHandler HasPendingRequestsChanged;

    #endregion

    #region IDisposable Members

    private bool m_disposed; //false

    protected override void Dispose( bool disposing )
    {
      if( !m_disposed )
      {
        InternalExpansionChangingEventManager.RemoveListener( m_groupHelper.GroupHandler, this );
        if( m_pendingRequestSource != null )
        {
          m_pendingRequestSource.HasPendingRequestsChanged -= this.OnHasPendingRequestsChanged;
        }

        m_dataHelper = null;
        m_disposed = true;
        base.Dispose( disposing );
      }
    }

    #endregion IDisposable Members

    #region IWeakEventListener Members

    bool ISLWeakEventListener.ReceiveWeakEvent( Type managerType, object sender, EventArgs e )
    {
      if( managerType == typeof( InternalExpansionChangingEventManager ) )
      {
        this.ExpansionChanging( sender, ( ExpansionChangedEventArgs )e );
        return true;
      }
      return false;
    }

    #endregion

    #region Private Fields

    private EstimateItemCount m_itemCount = new EstimateItemCount( 0, true, true );
    private RelativeOffset m_currentOffset = new RelativeOffset( 0, ReferencePoint.BeforeFirst );
    private SourcePosition m_currentPosition = SourcePosition.BeforeFirst;
    private IDataHelper<DataPath> m_dataHelper; //null
    private GroupHelper m_groupHelper; //null
    private IPendingRequests m_pendingRequestSource;//null
    private bool m_enableApproximateMoves = true;

    #endregion Private Fields

    private enum FetchStartPosition
    {
      SameAsInitial,
      Before,
      After
    }
  }
}
